# -*- coding: UTF-8 -*-
from json import JSONDecodeError

__author__ = 'zyhuang'

from git import Repo, InvalidGitRepositoryError, NoSuchPathError
from configparser import ConfigParser
import requests
import json
import os
import time


def current_info(_dir):
    """
    记录当前本地版本号、分支、git服务路径等信息（需要有专门的发布git服务器）
    :param _dir:
    :return:
    """
    try:
        repo = Repo(_dir)
        active_branch = repo.active_branch
        hcommit = repo.head.commit
    except InvalidGitRepositoryError:
        print("无效Git 目录")
    except NoSuchPathError:
        print("配置路径无效异常")
    return repo, active_branch, hcommit


class Upgrader(object):
    """
    升级器类
    """

    def __init__(self):
        """
        初始化升级器
        """

        self.cf = ConfigParser()
        self.cf.read('application.ini')
        self.process_name = self.cf.get("local", "process_name").strip()
        # 当前分析装置(每个设备唯一)
        self.device_code = self.cf.get("local", "device_code").strip()
        # 主分支仓库
        self.local_dir = self.cf.get("local", "local_dir_master").strip()
        # 从分支仓库
        # self.local_dir_slave = self.cf.get("local", "local_dir_slave")
        # 服务接口路径
        self.version_config_url = self.cf.get("service", "version_config_url").strip()
        self.notify_back_url = self.cf.get("service", "notify_back_url").strip()

        # 主仓库、活动分支、提交信息
        self.repo, self.active_branch, self.hcommit = current_info(self.local_dir)
        self.old_commit = self.hcommit.__str__()

        self.start_script = self.cf.get("local", "start_script").strip()
        self.stop_script = self.cf.get("local", "stop_script").strip()

        # 获取当前版本信息
        try:
            self.version_info = self.get_version_info()
        except JSONDecodeError:
            self.version_info = None
            print("JSON Decode Error, Not get version Info")

    def execute(self):
        """
        方法执行入口
        :return:
        """
        # 当前最新配置版本信息
        if not self.version_info:
            print("Not get version info.")
            # 没有获取到配置版本信息，静默处理
            return False
        # 1、对比版本更新和验证更新服务器地址(三重比较，确认是否可以更新)
        need_upgrade = self.has_new() and self.compare_git_url() and self.compare_git_branch()
        # 2、如果有版本更新并且时间符合范围，执行更新步骤
        if need_upgrade:
            print("begin to upgrade")
            cause = "Tip info:"
            # 3、关闭程序
            if not self.stop_program():
                cause += "-Close program error."
            # 4、拉取代码
            res = self.pull_new()
            if not res:
                cause += "-Pull code error."
            else:
                cause += "-"
            # 5、重启程序
            count = self.start_program()
            if count > 0:
                cause += "-Start program error."
                # 再次检测进程不符，则进行版本回滚(important),启动原版本项目
                if not self.exists_process(self.process_name):
                    # 重启原commit并启动原项目（第三次尝试启动原项目）
                    self.reset_commit(self.old_commit)
                    os.system(self.get_start_script())
                    cause += "-Start old version commit" + self.old_commit
                    print("Start old version program")

            if not self.exists_process(self.process_name):
                cause += "-After startup,not find process name:" + self.process_name + ", process name's id:" + self.process_name

            # 6、确认更新状态(再次对比commit)
            repo, active_branch, new_commit = current_info(self.local_dir)
            # 如果当前版本和服务器版本一致，则更新成功
            if self.version_info and ('lastCommit' in self.version_info) and (self.version_info['lastCommit'] == new_commit.__str__()):
                print("upgrade success ")
                # 7、推送分析装置更新状态（成功才会通知)
                upgrade_status = True

            else:
                cause = "- versions not the same"
                print("versions not the same, upgrade failed")
                upgrade_status = False

            self.version_info["cause"] = cause
            self.version_info["status"] = upgrade_status
            # 通知后台更新成功
            self.notify_back()

            return True
        else:
            print("Not need upgrade")
            return True

    def get_version_info(self):
        """
        调用服务接口
        :return:
        """
        rjson = requests.post(self.version_config_url, data={'anlyDevCode': self.device_code}).json()
        if rjson.__contains__("data"):
            return rjson.get("data")
        else:
            return None

    def stop_program(self):
        """
        调用关闭脚本停止当前服务
        :return:
        """
        os.system(self.stop_script)
        time.sleep(2)
        return True

    def start_program(self):
        '''
        调用启动脚本启动当前服务
        :return: 启动失败次数
        '''
        os.system(self.start_script)
        time.sleep(3)
        count = 0
        # 检测进程(如果不存在，至少失败执行了一次)
        if not self.exists_process(self.process_name):
            count = 1  # 失败次数
        # 再次执行脚本（如果已存在进程，会阻塞不能执行)
        if count > 0:
            os.system(self.start_script)
        if not self.exists_process(self.process_name):
            count = count + 1  # 失败次数

        return count

    def has_new(self):
        '''
        检测git当前分支代码是否有更新(首次)
        :return:
        '''
        # 双重比较判断是否有更新
        return self.version_info['lastCommit'].strip() != self.hcommit.__str__()

    def notify_back(self):
        '''
        执行版本更新之后，回调通知版本更新情况
        :return:
        '''
        # self.version_info['status'] = False
        resp = requests.post(self.notify_back_url, data=json.dumps(self.version_info),
                             headers={'Content-Type': 'application/json'})
        return resp.json()

    def pull_new(self):
        '''
        主分支拉取并验证处理
        拉取更新代码、模型和其他更新的文件
        :param _dir:
        :return:
        '''
        res = self.repo.remote().pull()

        return res.__str__()

    def compare_git_url(self):
        """
        通过对比本地远程URL确认更新同样的GIT项目
        :return:
        """
        urls = (url for url in self.repo.remote().urls)
        # 只要包含即可
        flag = self.version_info and self.version_info.__contains__('url') and (self.version_info['url'] in urls)
        if not flag:
            print("GIT URL not the same urls.")

        return flag

    def compare_git_branch(self):
        """
        通过对比本地远程分支确认更新同样的GIT分支
        :return:
        """
        current_branch_name = self.repo.active_branch.name
        flag = current_branch_name == self.version_info['branch']
        if not flag:
            print("分支版本不一致.")

        return flag

    def exists_process(self, proc_name):
        """
        根据进程名查找是否存在启动的进程(只要有一个存在，则判定进程存在,所以名称)
        :param proc_name:
        :return:
        """
        # 如果没有进程名，则直接返回False
        if not proc_name:
            return False

        # tmp_pids = psutil.pids()
        # for pid in tmp_pids:
        #    p = psutil.Process(pid)
        #    if p.name() == proc_name:
        #        print(pid)
        #        return True

        return True

    def reset_commit(self, target_commit_id):
        '''
        重置当前git仓库到目标版本commit
        :param target_commit_id:
        :return:
        '''
        git = self.repo.git
        ret_str = "" + git.execute("git reset --hard " + target_commit_id)
        # 如果开头字符为HEAD is now at，则说明还原版本成功
        return ret_str.startswith("HEAD is now at")


if __name__ == '__main__':
    upgrader = Upgrader()
    upgrader.execute()
    # some change
